package cn.xinfei.xdecision.common.service.script;

import cn.xinfei.xdecision.common.model.component.scriptrule.*;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;
import cn.xinfei.xdecision.common.service.script.groovy.ScriptTemplate;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import groovy.lang.MissingPropertyException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service(value = "expression")
public class ExpressionUtil {

    private static final List<String> BASIC_DATA_TYPES = Arrays.asList("integer", "double", "boolean", "string");

    public List<RuleMixExpressionCheckResult> testScriptRule(RuleMixExpressionCheck mixExpressionCheck) {
        String expression = mixExpressionCheck.getExpression();
        String expressionResultType = mixExpressionCheck.getExpressionResultType();
        log.info("表达式校验开始，id:{},expression:{},expressionResultType:{}", mixExpressionCheck.getId(), expression, expressionResultType);
        List<JSONObject> bindings = scriptRuleBinding(mixExpressionCheck.getMixExpressionTestDataList());

        if (bindings.isEmpty()) {
            log.error("表达式绑定数据为空！");
            throw new XDecisionDataException(XDecisionDataExceptionType.EXPRESSION_ERROR,
                    "表达式绑定数据为空！");
        }

        List<RuleMixExpressionCheckResult> checkResults = new ArrayList<>();
        for (JSONObject binding : bindings) {
            Map<String, RuleTestData> ruleTestDataMap = (Map<String, RuleTestData>) binding.get("binding");
            Integer rowId = binding.getInteger("row_id");
            String desiredResult = binding.getString("desired_result");
            RuleMixExpressionCheckResult checkResult = null;
            Object result;
            RuleScriptVO ruleScriptVO;
            try {
                result = ScriptTemplate.scriptExecution(expression, ruleTestDataMap);
                ruleScriptVO = JSON.parseObject(JSON.toJSONString(result), RuleScriptVO.class);
                if (StringUtils.isNotEmpty(desiredResult) && !resultDataCheck(expressionResultType, desiredResult, ruleScriptVO.getRuleHitResult())) {
                    //测试不通过，与期望结果不符
                    checkResult = new RuleMixExpressionCheckResult(
                            rowId,
                            "fail",
                            0,
                            ruleScriptVO.getContext(),
                            String.format("与期望结果不符。期望的计算结果：%s，实际的计算值：%s",
                                    desiredResult, JSON.toJSONString(ruleScriptVO.getRuleHitResult())));
                }

                if (checkResult == null) {
                    checkResult = new RuleMixExpressionCheckResult(
                            rowId,
                            ruleScriptVO.getRuleHitResult(),
                            ruleScriptVO.getRuleHitScore(),
                            ruleScriptVO.getContext(),
                            "测试通过");
                }
                checkResults.add(checkResult);
            } catch (MissingPropertyException e1) {
                log.error("", e1);
                checkResult = new RuleMixExpressionCheckResult(
                        rowId,
                        "fail",
                        0,
                        new HashMap<>(),
                        "解析失败，表达式里存在未选择变量");
                checkResults.add(checkResult);
            } catch (MultipleCompilationErrorsException e2) {
                log.error("", e2);
                checkResult = new RuleMixExpressionCheckResult(
                        rowId,
                        "fail",
                        0,
                        new HashMap<>(),
                        "解析失败，符号使用错误");
                checkResults.add(checkResult);
            } catch (Exception e3) {
                log.error("", e3);
                checkResult = new RuleMixExpressionCheckResult(
                        rowId,
                        "fail",
                        0,
                        new HashMap<>(),
                        "解析失败");
                checkResults.add(checkResult);
            }
        }
        log.info("表达式校验结束");
        return checkResults;
    }

    /**
     * 表达式测试数据绑定
     *
     * @param testDatas 测试数据集
     * @return key:测试数据行号，value:Binding
     */
    private List<JSONObject> scriptRuleBinding(List<RuleMixExpressionTestData> testDatas) {
        List<JSONObject> bindings = new ArrayList<>();
        if (testDatas == null || testDatas.isEmpty()) {
            return bindings;
        }

        for (RuleMixExpressionTestData testData : testDatas) {
            JSONObject bindingObj = new JSONObject();
            //一组测试数据
            List<RuleTestData> datas = testData.getTestDatas();
            if (datas == null || datas.isEmpty()) {
                break;
            }
            Map<String, RuleTestData> testDataMap = datas.stream()
                    .filter(Objects::nonNull)
                    .collect(Collectors.toMap(RuleTestData::getVarCode, Function.identity()));
            bindingObj.put("row_id", testData.getRowId());
            bindingObj.put("binding", testDataMap);
            bindingObj.put("desired_result", testData.getDesiredResult());
            bindings.add(bindingObj);
        }
        return bindings;
    }

    /**
     * 数据类型校验
     *
     * @param desiredType   支持："integer","double","boolean","string"
     * @param desiredResult 期望的结果
     * @param obj           需要校验的数据
     * @return 校验结果
     */
    public static boolean resultDataCheck(String desiredType, String desiredResult, Object obj) {
        if (obj == null) {
            return false;
        }
        //默认按照字符串进行校验
        if (StringUtils.isBlank(desiredType)) {
            desiredType = "string";
        }
        if (BASIC_DATA_TYPES.contains(desiredType)) {
            switch (desiredType) {
                case "integer":
                    if (obj instanceof BigDecimal) {
                        BigDecimal result = (BigDecimal) obj;
                        if (result.intValue() == Integer.parseInt(desiredResult)) {
                            return true;
                        }
                    } else if (obj instanceof Integer) {
                        Integer result = (Integer) obj;
                        if (result.equals(Integer.valueOf(desiredResult))) {
                            return true;
                        }
                    }

                case "double":
                    if (obj instanceof BigDecimal) {
                        BigDecimal result = (BigDecimal) obj;
                        if (result.doubleValue() == Double.parseDouble(desiredResult)) {
                            return true;
                        }
                    } else if (obj instanceof Double) {
                        Double result = (Double) obj;
                        if (result.equals(Double.valueOf(desiredResult))) {
                            return true;
                        }
                    }
                case "string":
                    if (obj instanceof String) {
                        String result = (String) obj;
                        if (result.equals(desiredResult)) {
                            return true;
                        }
                    } else {
                        //兼容默认值
                        String result = obj.toString();
                        if (result.equals(desiredResult)) {
                            return true;
                        }
                    }
                case "boolean":
                    if (obj instanceof Boolean) {
                        Boolean result = (Boolean) obj;
                        if (result.equals(Boolean.valueOf(desiredResult))) {
                            return true;
                        }
                    }
            }
        }
        return false;
    }
}
